package de.fhkl.whatsstroke;

import java.net.InetAddress;
import java.net.UnknownHostException;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.annotation.TargetApi;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkInfo.State;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import de.fhkl.whatsstroke.StrokeService.StrokeBinder;
import de.fhkl.whatsstroke.utilities.Utilities;

/**
 * Activity which displays a login screen to the user, offering registration as
 * well.
 */
public class Login extends Activity
{
	private static final String EMAIL = "email";
	private static final String SERVER_ADDRESS = "serverAddress";
	private static final String SERVER_PORT = "serverPort";

	/** Shared preferences to save server configs and user email */
	private SharedPreferences mPreferences;

	/** Indicates whether a this login was successful. */
	private boolean mIsLoginSuccessful;

	// GUI references.
	private EditText mEmailView;
	private EditText mPasswordView;
	private View mLoginFormView;
	private View mLoginStatusView;
	private TextView mLoginStatusMessageView;

	/** BroadcastReceiver for broadcast intents from the service */
	private BroadcastReceiver mReceiver;

	/**
	 * ServiceConnection to retrieve a reference for a save way to call methods
	 * from the service
	 */
	private ServiceConnection mConnection;

	/** Reference to the underlying service */
	private StrokeService mService;

	/** Indicates whether the service is bound to this activity */
	private boolean mIsServiceBound;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);

		setContentView(R.layout.activity_login);

		mPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());

		// Set up the login form.
		String email = mPreferences.getString(EMAIL, "");
		mEmailView = (EditText) findViewById(R.id.email);
		mEmailView.setText(email);

		mPasswordView = (EditText) findViewById(R.id.password);
		mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener()
		{
			@Override
			public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent)
			{
				if (id == R.id.login || id == EditorInfo.IME_NULL)
				{
					attemptLogin();
					return true;
				}
				return false;
			}
		});

		mLoginFormView = findViewById(R.id.login_form);
		mLoginStatusView = findViewById(R.id.login_status);
		mLoginStatusMessageView = (TextView) findViewById(R.id.login_status_message);

		findViewById(R.id.sign_in_button).setOnClickListener(new View.OnClickListener()
		{
			@Override
			public void onClick(View view)
			{
				attemptLogin();
			}
		});

		Intent intent = new Intent(getApplicationContext(), StrokeService.class);
		getApplicationContext().startService(intent);

		mConnection = new StrokeServiceConnection();
		mReceiver = new StrokeBroadcastReceiver();

		mIsServiceBound = false;
		mIsLoginSuccessful = false;

		// check whether the server configs are already set
		if (TextUtils.isEmpty(mPreferences.getString(SERVER_ADDRESS, "")) || mPreferences.getInt(SERVER_PORT, 0) == 0)
		{
			createConfigDialog();
		}
	}

	/**
	 * Creates a dialog that allows the user to set server configs.
	 */
	private void createConfigDialog()
	{
		LayoutInflater layoutInflater = getLayoutInflater();
		View view = layoutInflater.inflate(R.layout.settings_dialog, null);
		final EditText txtServerAddress = (EditText) view.findViewById(R.id.server_address);
		final EditText txtServerPort = (EditText) view.findViewById(R.id.server_port);

		txtServerAddress.setText(mPreferences.getString(SERVER_ADDRESS, ""));
		txtServerPort.setText("" + mPreferences.getInt(SERVER_PORT, 50001));

		AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle(R.string.server_config);
		builder.setView(view);

		final AlertDialog dialog = builder.create();

		Button saveButton = (Button) view.findViewById(R.id.save_server_config);
		saveButton.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View v)
			{
				Editor editor = mPreferences.edit();

				boolean success = true;

				// check whether the specified server address is valid
				try
				{
					String serverAddress = txtServerAddress.getText().toString();

					if (!serverAddress.equals(""))
					{

						InetAddress inetAddress = InetAddress.getByName(serverAddress);

						if (inetAddress != null)
						{
							editor.putString(SERVER_ADDRESS, serverAddress);
						}
						else
						{
							throw new UnknownHostException();
						}
					}
					else
					{
						txtServerAddress.setError(getText(R.string.invalid_server_address));
						success = false;
					}
				}
				catch (UnknownHostException e)
				{
					txtServerAddress.setError(getText(R.string.invalid_server_address));
					success = false;
				}

				String strServerPort = txtServerPort.getText().toString();

				if (strServerPort != null && !strServerPort.equals(""))
				{
					editor.putInt(SERVER_PORT, Integer.parseInt(strServerPort));
				}
				else
				{
					txtServerPort.setError(getText(R.string.invalid_server_port));
					success = false;
				}

				if (success)
				{
					editor.commit();
					dialog.dismiss();
				}
			}
		});

		Button cancelButton = (Button) view.findViewById(R.id.cancel_server_config);
		cancelButton.setOnClickListener(new OnClickListener()
		{
			@Override
			public void onClick(View v)
			{
				dialog.dismiss();
			}
		});

		dialog.show();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu)
	{
		super.onCreateOptionsMenu(menu);
		getMenuInflater().inflate(R.menu.activity_login, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item)
	{
		switch (item.getItemId())
		{
			case R.id.menu_server_config:
				createConfigDialog();
			break;
		}
		
		return true;
	}

	@Override
	protected void onStart()
	{
		super.onStart();

		this.bindService(new Intent(this, StrokeService.class), mConnection, 0);
		this.registerReceiver(mReceiver, new IntentFilter(Broadcast.LOGIN_ANSWER));
	}

	@Override
	protected void onStop()
	{
		if (mIsServiceBound)
		{
			this.unbindService(mConnection);
		}

		this.unregisterReceiver(mReceiver);

		super.onStop();
	}

	@Override
	public void onBackPressed()
	{
		if (!mIsLoginSuccessful)
		{
			if (mIsServiceBound)
			{
				// unbind service to allow stopping it explicitly in a save way
				// without leaking a service connection
				this.unbindService(mConnection);

				// make sure on stop() does not try to unbind the service again
				mIsServiceBound = false;
			}
			// stop the service
			getApplicationContext().stopService(new Intent(getApplicationContext(), StrokeService.class));

			finish();
		}
	}

	/**
	 * Attempts to sign in or register the account specified by the login form.
	 * If there are form errors (invalid email, missing fields, etc.), the
	 * errors are presented and no actual login attempt is made.
	 */
	public void attemptLogin()
	{
		// Reset errors.
		mEmailView.setError(null);
		mPasswordView.setError(null);

		// Store values at the time of the login attempt.
		String email = mEmailView.getText().toString();
		String password = mPasswordView.getText().toString();

		boolean cancel = false;
		View focusView = null;

		// Check for a valid password.
		if (TextUtils.isEmpty(password))
		{
			mPasswordView.setError(getString(R.string.error_field_required));
			focusView = mPasswordView;
			cancel = true;
		}
		else if (password.length() < 6)
		{
			mPasswordView.setError(getString(R.string.error_invalid_password));
			focusView = mPasswordView;
			cancel = true;
		}
		
		// encrypt password
		password = Utilities.generateMd5Sum(password);

		// Check for a valid email address.
		if (TextUtils.isEmpty(email))
		{
			mEmailView.setError(getString(R.string.error_field_required));
			focusView = mEmailView;
			cancel = true;
		}
		else if (!email.contains("@"))
		{
			mEmailView.setError(getString(R.string.error_invalid_email));
			focusView = mEmailView;
			cancel = true;
		}
		else
		{
			Editor editor = mPreferences.edit();
			editor.putString(EMAIL, email);
			editor.commit();
		}

		if (cancel)
		{
			// There was an error; don't attempt login and focus the first
			// form field with an error.
			focusView.requestFocus();
		}
		else
		{
			// Show a progress spinner, and kick off a background task to
			// perform the user login attempt.
			mLoginStatusMessageView.setText(R.string.login_progress_signing_in);

			AsyncTask<String, Void, Boolean> at = new AsyncTask<String, Void, Boolean>()
			{
				@Override
				protected Boolean doInBackground(String... s)
				{
					mIsLoginSuccessful = mService.login(s[0], s[1]);

					return mIsLoginSuccessful;
				}

				@Override
				protected void onPostExecute(Boolean result)
				{
					if (!result)
					{
						showProgress(false);

						// Get system service to access the network connectivity
						// state
						ConnectivityManager manager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);

						AlertDialog.Builder builder = new AlertDialog.Builder(Login.this);
						builder.setNeutralButton(R.string.ok, new DialogInterface.OnClickListener()
						{
							@Override
							public void onClick(DialogInterface dialog, int id)
							{
								dialog.dismiss();
							}
						});

						if (manager != null)
						{
							// State of mobile connection
							State mobile = manager.getNetworkInfo(0).getState();

							// State of wireless connection
							State wifi = manager.getNetworkInfo(1).getState();

							if (mobile == NetworkInfo.State.CONNECTED || wifi == NetworkInfo.State.CONNECTED)
							{
								builder.setMessage(getString(R.string.error_server_not_available)).create().show();
								Log.w("Login", "Server not available!");
							}
							else if (mobile == NetworkInfo.State.DISCONNECTED || mobile == NetworkInfo.State.DISCONNECTED)
							{
								builder.setMessage(getString(R.string.error_network_disabled)).create().show();
								Log.w("Login", "Network disabled!");
							}
						}
						else
						{
							builder.setMessage(getString(R.string.error_network_disabled)).create().show();
							Log.w("Login", "Network disabled!");
						}
					}
				}
			};
			at.execute(email, password);
			showProgress(true);
		}
	}

	private void forwardToOutbox()
	{
		Intent intent = new Intent(this, Outbox.class);
		startActivity(intent);
		finish();
	}

	/**
	 * Shows the progress UI and hides the login form.
	 */
	@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
	private void showProgress(final boolean show)
	{
		// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
		// for very easy animations. If available, use these APIs to fade-in
		// the progress spinner.
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2)
		{
			int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);

			mLoginStatusView.setVisibility(View.VISIBLE);
			mLoginStatusView.animate().setDuration(shortAnimTime).alpha(show ? 1 : 0).setListener(new AnimatorListenerAdapter()
			{
				@Override
				public void onAnimationEnd(Animator animation)
				{
					mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
				}
			});

			mLoginFormView.setVisibility(View.VISIBLE);
			mLoginFormView.animate().setDuration(shortAnimTime).alpha(show ? 0 : 1).setListener(new AnimatorListenerAdapter()
			{
				@Override
				public void onAnimationEnd(Animator animation)
				{
					mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
				}
			});
		}
		else
		{
			// The ViewPropertyAnimator APIs are not available, so simply show
			// and hide the relevant UI components.
			mLoginStatusView.setVisibility(show ? View.VISIBLE : View.GONE);
			mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
		}
	}

	/**
	 * Safe connection to the underlying service over a returned weak reference.
	 * Like many callbacks from the system, the methods on this class are called
	 * from the main thread of your process.
	 */
	public class StrokeServiceConnection implements ServiceConnection
	{
		@Override
		public void onServiceConnected(ComponentName name, IBinder binder)
		{
			if (!mIsServiceBound)
			{
				mService = ((StrokeBinder) binder).getService();

				if (mService.isLoggedIn())
				{
					forwardToOutbox();
				}

				mIsServiceBound = true;

				Log.i("StrokeServiceConnection", "Service " + mService);
			}
		}

		@Override
		public void onServiceDisconnected(ComponentName name)
		{
			mIsServiceBound = false;
		}
	}

	/**
	 * Broadcast receiver to receive and react to broadcast messages from the
	 * underlying service.
	 */
	public class StrokeBroadcastReceiver extends BroadcastReceiver
	{
		@Override
		public void onReceive(Context context, Intent intent)
		{
			String action = intent.getAction();

			if (action.equals(Broadcast.LOGIN_ANSWER))
			{
				Bundle extras = intent.getExtras();
				boolean isSuccessful = extras.getBoolean("de.fhkl.whatsstroke.LoginAnswer");

				if (isSuccessful)
				{
					forwardToOutbox();
				}
				else
				{
					mIsLoginSuccessful = false;
					mPasswordView.setError(getString(R.string.error_incorrect_password));
					mPasswordView.requestFocus();
					showProgress(false);
				}
			}
		}
	}
}
